package com.googlecode.afdbaccess.teshelpers;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import junit.framework.Assert;

import org.mockito.BDDMockito;
import org.mockito.Mockito;

public class ArrayBackedResultSet implements InvocationHandler {
    private int rowCounter = -1;
    private boolean isWasNull = false;
    private Map<String, Integer> columnMap;
    
    private Object[][] values;
    private ResultSetMetaData mdMock;
    
    public ArrayBackedResultSet(String[] columnLabels, int[] columnTypes, Object[][] values) {
        Assert.assertTrue("Bad parameters to fill resultSet mock (needs to be the same number of columns for labels, metadata, values)", columnLabels.length == columnTypes.length && (values.length == 0 || columnLabels.length == values[0].length));

        this.values = values;
        this.mdMock = mockMetadata(columnLabels, columnTypes);
        this.columnMap = getColumnMap(columnLabels);
    }

    private Map<String, Integer> getColumnMap(String[] columnLabels) {
        Map<String, Integer> returnObject = new HashMap<String, Integer>();
        
        for(int counter = 0; counter < columnLabels.length; counter++) {
            String key = columnLabels[counter];
            returnObject.put(key, new Integer(counter));
        }

        return returnObject;
    }

    @SuppressWarnings("boxing")
    private static ResultSetMetaData mockMetadata(String[] columnLabels, int[] columnTypes) {
        ResultSetMetaData mdMock = Mockito.mock(ResultSetMetaData.class);
        try {
            BDDMockito.given(mdMock.getColumnCount()).willReturn(columnLabels.length); // Always the same
            
            for(int counter = 0; counter < columnLabels.length; counter++) {
                BDDMockito.given(mdMock.getColumnLabel(counter+1)).willReturn(columnLabels[counter]);
                BDDMockito.given(mdMock.getColumnType(counter+1)).willReturn(columnTypes[counter]);
            }
        } catch (SQLException exception) {
            // Shouldn't happen, so wrapping it in a runtime exception
            throw new RuntimeException(exception);
        }
        
        return mdMock;
    }    
    
    public static ResultSet getInstance(String[] columnLabels, int[] columnTypes, Object[][] values) {
        return (ResultSet)Proxy.newProxyInstance(ResultSet.class.getClassLoader(),
                                             new Class[] { ResultSet.class },
                                             new ArrayBackedResultSet(columnLabels, columnTypes, values));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Find the equivalent method in the proxy class.
        Method m = getClass().getMethod(method.getName(), method.getParameterTypes());
        if(m == null) {
            throw new SQLException("Unsupported method " + method.getName());
        }

        return m.invoke(this, args);
    }

    // Overridden methods
    
    public int findColumn(String columnLabel) {
        return columnMap.get(columnLabel).intValue();
    }

    public boolean next() {
        if(rowCounter+1 < values.length) {
            rowCounter++;
            return true;
        } else {
            return false;
        }
    }
    
    public boolean previous() {
        if(rowCounter >= 0) {
            rowCounter--;
            return true;
        } else {
            return false;
        }
    }

    public void beforeFirst() {
        rowCounter = -1;
    }
    
    public boolean isBeforeFirst() {
        return rowCounter < 0;
    }
    
    public void afterLast() {
        rowCounter = values.length;
    }
    
    boolean isAfterLast() {
        return rowCounter >= values.length;
    }
    
    public ResultSetMetaData getMetaData() {
        return mdMock;
    }

    public Object getObject(int columnIndex) throws SQLException {
        if(isBeforeFirst()) {
            throw new SQLException("Before first");
        }
        
        if(isAfterLast()) {
            throw new SQLException("After last");
        }

        Object returnValue = values[rowCounter][columnIndex-1];
        isWasNull = (returnValue == null);
        
        return returnValue;
    }
    
    public Object getObject(String columnLabel) throws SQLException {
        return getObject(findColumn(columnLabel));
    }

    public String getString(int columnIndex) throws SQLException {
        return ((String)getObject(columnIndex));
    }

    public String getString(String columnLabel) throws SQLException {
        return getString(findColumn(columnLabel));
    }

    public long getLong(int columnIndex) throws SQLException {
        return ((Long)getObject(columnIndex)).longValue();
    }

    public long getLong(String columnLabel) throws SQLException {
        return getLong(findColumn(columnLabel));
    }
    
    public int getInt(int columnIndex) throws SQLException {
        return (int)getLong(columnIndex);
    }

    public int getInt(String columnLabel) throws SQLException {
        return getInt(findColumn(columnLabel));
    }

    public boolean wasNull() {
        return isWasNull;
    }
}